package tcln.sors.client.core.mvc.modelmapping;

import java.lang.reflect.Method;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import tcln.commons.assertion.Assert;
import tcln.commons.beans.BeanUtils;
import tcln.commons.exception.BaseRuntimeException;
import tcln.sors.client.core.mvc.ModelData;

/**
 * @author Truong Chau Lien Ngoc (lienngoc@gmail.com)
 * 
 */
public class DefaultModelDataPopulator implements ModelDataPopulator {

    /**
     * {@inheritDoc}
     */
    @Override
    public <E> E populate(ModelData<?> modelData, E destObject, boolean full) {
        Assert.ARGUMENTS.notNull(modelData, "Model data must not be null");
        Assert.ARGUMENTS.notNull(destObject, "Destination object must not be null");

        Map<String, Object> properties = modelData.toMap(full);

        try {
            for (Entry<String, Object> entry : properties.entrySet()) {
                String writeMethodName = BeanUtils.getWriteMethodName(entry.getKey());

                // find write-method in the dest object.
                // NOTE:
                // Since property value may be NULL, it makes the system impossible to detect method signature
                // correctly => search all possible methods with the same name. In this case, 'multiple overloads' is
                // prohibited.
                List<Method> methods = BeanUtils.findAllMethods(destObject, writeMethodName);
                Assert.STATE.isFalse(methods.size() >= 2,
                        "More than 1 overload of write-method '{0}', which is prohibited for object population",
                        writeMethodName);
                Method writeMethod = methods.isEmpty() ? null : methods.get(0);

                if (writeMethod != null) {
                    writeMethod.setAccessible(true);
                    writeMethod.invoke(destObject, entry.getValue());
                }
            }
        } catch (Exception e) {
            throw new BaseRuntimeException(e);
        }

        return destObject;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public <E> E populate(ModelData<?> modelData, Class<E> destObjectClass, boolean full) {
        Assert.ARGUMENTS.notNull(modelData, "Model data must not be null");
        Assert.ARGUMENTS.notNull(destObjectClass, "Destination object class must not be null");

        E destObject = BeanUtils.newInstance(destObjectClass);
        return populate(modelData, destObject, full);
    }
}
